package fileDistributionStatistics;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.ObjectInputStream.GetField;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.TreeMap;

public class FileSizeDistributionCalculation {

	public final int CLUSTERSIZE = 4096;
	final int MINFILESIZE = 1;
	final int FILESIZEFOROPTIMALFRAG = 524288;
	/*
	 * original wastage value considered is 258,410,819
	 */
	
//	int WASTAGEVALUE = 250;
	int WASTAGEVALUE = 250;
	double Reliability = .999999999999;
	int numberOfSourceFragments ;
	double serverAvailabilityProbability = .9;
	Double sourceFileLength = (double) 0;
	double fragmentSize = 0;
	double totalEncodedFragments = 0;
	public FileWriter fstream;
	public BufferedWriter out;
	
	public int getWASTAGEVALUE() {
		return WASTAGEVALUE;
	}

	public void setWASTAGEVALUE(int wASTAGEVALUE) {
		WASTAGEVALUE = wASTAGEVALUE;
	}

	public double getTotalEncodedFragments() {
		return totalEncodedFragments;
	}

	public void setTotalEncodedFragments(double totalEncodedFragments) {
		this.totalEncodedFragments = totalEncodedFragments;
	}

	public double getFragmentSize() {
		return fragmentSize;
	}

	public void setFragmentSize(double fragmentSize) {
		this.fragmentSize = fragmentSize;
	}

	public double getReliability() {
		return Reliability;
	}

	public void setReliability(double reliability) {
		Reliability = reliability;
	}

	public double getServerAvailabilityProbability() {
		return serverAvailabilityProbability;
	}

	public void setServerAvailabilityProbability(
			double serverAvailabilityProbability) {
		this.serverAvailabilityProbability = serverAvailabilityProbability;
	}

	

	public Double getSourceFileLength() {
		return sourceFileLength;
	}

	public void setSourceFileLength(Double sourceFileLength) {
		this.sourceFileLength = sourceFileLength;
	}

	public int getNumberOfSourceFragments() {
		return numberOfSourceFragments;
	}

	public void setNumberOfSourceFragments(int numberOfSourceFragments) {
		this.numberOfSourceFragments = numberOfSourceFragments;
	}
 
	
	
	public FileSizeDistributionCalculation() {
		super();
		setReliability(Reliability);
		setServerAvailabilityProbability(serverAvailabilityProbability);
//		File filename = new File("C:" + File.separator + "Users"
//				+ File.separator + "Bhavya" + File.separator + "Desktop"
//				+ File.separator + "Cloud_Computing_FYP" + File.separator
//				+ "FileFragmentSizeCalculation" + File.separator
//				+ "UnixGreaterthan40.txt");
		
	}

	
	
//	public static void main(String[] args) {
//		try {
//			// Create file
//			File filename = new File("/home/bhavya/Desktop/BHAVYA_FYP/Cloud_Computing_FYP/FileFragmentSizeCalculation_2/sample.txt");
//			FileWriter fstream = new FileWriter(filename);
//			BufferedWriter out = new BufferedWriter(fstream);
//
//			FileSizeDistributionCalculation f = new FileSizeDistributionCalculation();
////			f.setReliability(f.Reliability);
////			f.setServerAvailabilityProbability(f.serverAvailabilityProbability);
//			f.sourceFileLength = Math.abs((double) (524288.0));
//			Double end_value=(double) 602379;
//			// f.setParameters();
////			System.out.println(f.sourceFileLength);
//			while (f.sourceFileLength <= end_value){
////				System.out.println(f.sourceFileLength);
//				f.numberOfSourceFragments = 174;
//				f.setNumberOfSourceFragments(f.numberOfSourceFragments);
//				f.setSourceFileLength(f.sourceFileLength);
//				System.out.println(f.sourceFileLength + ",");
//				f.fragmentSize = f.calculateFragmentSize();
//				System.out.println(f.fragmentSize + ",");
//				double r = f.setParameters();
////				System.out.println(" " + f.getNumberOfSourceFragments() + ",");
////				System.out.println(" " + f.getTotalEncodedFragments() + ",");
//				// out.write(r);
////				out.write(String.valueOf(r));
//				System.out.println(r/f.getSourceFileLength());
//				System.out.println();
////				out.newLine();
//				f.sourceFileLength += 1;
//			} 
//			out.close();
//		} catch (Exception e) {// Catch exception if any
//			System.err.println("Error: " + e.getMessage());
//		}
//	}

	/*
	 * Calculate the optimal fragment size
	 */
	public double calculateFragmentSize() {
		double sourceFileSize = getSourceFileLength();
		int numberOfSourceFrag = getNumberOfSourceFragments();
		double fragSize = 0;
		int modFragSize = 0;

		if (sourceFileSize > MINFILESIZE
				&& sourceFileSize < FILESIZEFOROPTIMALFRAG) {
			/*
			 * for file size greater than 40KB and less than 512KB fragment size
			 * is equal to the CLUSTER_SIZE
			 */
			fragSize = CLUSTERSIZE;
		} else {
			/*
			 * file size greater than 512 KB perform the below operation
			 */
			if (sourceFileSize >= FILESIZEFOROPTIMALFRAG) {
				/*
				 * if file size is a multiple of 2 then fragSize is
				 * filesize/128;
				 */
				fragSize = Math.ceil((double)sourceFileSize / numberOfSourceFrag);
				modFragSize = (int) (fragSize % CLUSTERSIZE);
				if (modFragSize == 0) {
					// System.out.println(" no problem here");
				} else {
					/*
					 * calculate the optimal number of fragments
					 */

					numberOfSourceFrag = optimalFragmentSize();
					setNumberOfSourceFragments(numberOfSourceFrag);
					fragSize = Math.ceil((double) sourceFileSize / getNumberOfSourceFragments());
					// System.out
					// .println(" modify the number of source frag and determine opt frag size");
				}
			}
		}
		return fragSize;
	}
	
	/*
	 * code to find the optimal number of fragments
	 */
	private int optimalFragmentSize() {
		TreeMap<Double, Integer> sortedMap = null;
		TreeMap<Integer, Integer> decreaseMap = null;
		/*
		 * initialization
		 */
		double sourceFileSize = getSourceFileLength();
		int numberOfSourceFrag = getNumberOfSourceFragments();
		double fragSize =  Math.ceil ((double)sourceFileSize / numberOfSourceFrag);
		int optNumberOfFrag = 0;
		int val1 = 0, val2 = 0;
		/*
		 * fragSize%CLUSTERSIZE will be the extra space occupied in the new
		 * cluster modfrag(wastage)=CLUSTERSIZE-occupied aim is to reduce this
		 * as much as possible
		 */
		int modFrag = (int) Math.ceil (CLUSTERSIZE - (fragSize % CLUSTERSIZE));
		/*
		 * HashMap<int modFragSize, int numSrcFrg > map = new HashMap<K, V>();
		 * Since we are not sure which fragment size/ number of fragments will
		 * be optimal around 128 we try to find the size by increasing the
		 * number of source fragments and by reducing the number of source
		 * fragments around INITIAL
		 */
		HashMap<Double, Integer> increasingFragNumber = new HashMap<Double, Integer>();
		int i=0;
		while ((modFrag >= WASTAGEVALUE && (modFrag%CLUSTERSIZE)!=0)) {
			i++;
			if(i>=1000){
				break;
			}
			numberOfSourceFrag++;
			fragSize =Math.ceil((double)sourceFileSize / numberOfSourceFrag);
			/*
			 * calculate new wastage after increasing number of source frags
			 */
			modFrag = (int) (CLUSTERSIZE - (fragSize % CLUSTERSIZE));
			
			increasingFragNumber.put(fragSize, numberOfSourceFrag);
			// System.out.println(" decresing"+numberOfSourceFrag+" "+fragSize);
		}
		/*
		 * initialize again and find the optimal fragment number by reducing
		 * against INITIAL
		 */
		sourceFileSize = getSourceFileLength();
		numberOfSourceFrag = getNumberOfSourceFragments();
		fragSize = sourceFileSize / numberOfSourceFrag;
		modFrag = (int) Math.ceil(CLUSTERSIZE - (fragSize % CLUSTERSIZE));
		HashMap<Integer, Integer> decreasingFragNumber = new HashMap<Integer, Integer>();
		// TODO: handle when both ash maps are empty
		/*
		 * sort both the maps to obtain the numberOfSrcFrag depending on the
		 * least wastage value
		 */
		if (!increasingFragNumber.isEmpty()) {
			/**
			 * adding code here to calculate the number of encoded fragments and multiplication of number of encoded frags 
			 * and frag size find the number of source frags where this product value is the least. 
			 */
			double delta = Math.pow(1 - getReliability(), -1);
			
			double E = Math.log(delta) / Math.log(2);
			HashMap<Double,Integer> pdtSourceFrag= new HashMap<Double,Integer>();
			for ( double sourceFrag : increasingFragNumber.keySet()){
				int N = (int) (increasingFragNumber.get(sourceFrag)+ Math.ceil(E));
				int fs=(int) sourceFrag;
				if( fs%4096!=0){
				fs=fs+(4096-(fs%4096));	
				}
				Double pdt=Math.ceil(N*fs);
				pdtSourceFrag.put(pdt, increasingFragNumber.get(sourceFrag));
			}
			sortedMap = new TreeMap<Double, Integer>(pdtSourceFrag);
			val1 = sortedMap.get(sortedMap.firstKey());
		} else {
			val1=getNumberOfSourceFragments();
		}
		
		if (!decreasingFragNumber.isEmpty()) {
			decreaseMap = new TreeMap<Integer, Integer>(decreasingFragNumber);
			val2 = decreaseMap.descendingKeySet().first();
		}
		 return val1;
		/*
		 * cases when one of the TreeMaps are empty when both are available and
		 * when both are empty
		 */
		// TODO what to do when the resulting number of source fragments causes
		// too much redundancy

//		if (!increasingFragNumber.isEmpty() 
//				&& decreasingFragNumber.isEmpty()) {
//		
//			optNumberOfFrag = val1;
//		
//		} else if (!decreasingFragNumber.isEmpty()
//				&& increasingFragNumber.isEmpty()) {		
//			optNumberOfFrag = sortedMap.get(val2);
//
//		} else {
//			/*
//			 * selecting the numberOfSourceFrag which is closet to INITIAL so
//			 * that redundancy is less
//			 */
//			if (decreasingFragNumber.isEmpty() && increasingFragNumber.isEmpty()) {
//				optNumberOfFrag = getNumberOfSourceFragments();
//			} else {
//				if (Math.abs(sortedMap.get(val1) - getNumberOfSourceFragments()) < Math
//						.abs(decreaseMap.get(val2) - getNumberOfSourceFragments())) {
//					optNumberOfFrag = val1;
//				} else {
//					optNumberOfFrag = decreaseMap.get(val2);
//				}
//			}
//			
//		}
//		return optNumberOfFrag;
	}

	public double setParameters() {
		double temp = Math.ceil((double) getSourceFileLength()
				/ getFragmentSize());
		setNumberOfSourceFragments((int) temp);
		// System.out.print(getNumberOfSourceFragments()+",");
		double delta = Math.pow(1 - getReliability(), -1);
		// System.out.println(delta);
		double E = Math.log(delta) / Math.log(2);
		// System.out.println(E);
		int N = (int) (getNumberOfSourceFragments() + Math.round(E));
		// System.out.println(N);
		double TotalEncodedPackets = Math.ceil( (N / getServerAvailabilityProbability()));
		setTotalEncodedFragments(TotalEncodedPackets);
		// System.out.print(TotalEncodedPackets);
		double factor = Math.ceil(((double) getFragmentSize() % CLUSTERSIZE));
		double fragHardDiskSize=0.0;
		if( factor!=0){
		fragHardDiskSize = (int) getFragmentSize()+(CLUSTERSIZE-factor);
	}else{
		fragHardDiskSize=getFragmentSize();
	}
		
		Double totalEncodedSpaceOccupied = (double) (TotalEncodedPackets * fragHardDiskSize);
//		double redundancy = (double) (TotalEncodedPackets * (fragHardDiskSize))
//				/ (srcHardDiskSize);
		return totalEncodedSpaceOccupied;
	}
}
